using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using ChessKit.Common;
using ChessKit.Ui.ChessPanel;

namespace ChessKit.Ui
{
  internal class Layout : ISlaveLayout
  {
    public IBoardLocationTransform Transform { get; set; }
    public Size RenderSize { get; private set; }
    public double ActualWidth { get { return RenderSize.Width; } }
    public double ActualHeight { get { return RenderSize.Height; } }

    public Layout()
    {
      Transform = BoardTransform.Identity;
      Measure(Enumerable.Empty<UIElement>(), new Size());
    }

    #region ' Fields '

    private Dictionary<BoardLocation, Size> _measures;
    private Dictionary<BoardArea, Size> _areas;
    private double _cellSize;
    private double _spearHeight;
    private double _spearWidth;
    private readonly double[] _rows = new double[9];
    private readonly double[] _cols = new double[9];

    private double _vertEdgesWidth;
    private double _horizEdgesHeight;
    private double _columnsWidth;
    private double _rowsHeight;

    private static readonly BoardArea[] MeasuredAreas =
      new[]
        {
          BoardArea.LeftSide, BoardArea.RightSide, BoardArea.TopSide, BoardArea.BottomSide,
          BoardArea.Cell, 
        };

    private Size _availableSize;

    #endregion

    public void Measure(IEnumerable<UIElement> elements, Size availableSize)
    {
      _availableSize = availableSize;
      _measures = MeasureDic(elements);
      _areas = MeasureAreas(_measures);


      _vertEdgesWidth =
        _areas[BoardArea.LeftSide].Width +
        _areas[BoardArea.RightSide].Width;

      _horizEdgesHeight =
        _areas[BoardArea.TopSide].Height +
        _areas[BoardArea.BottomSide].Height;

      _columnsWidth = Enumerable.Range(0, 9).Sum(i => GetColumnWidth(i));
      _rowsHeight = Enumerable.Range(0, 9).Sum(i => GetRowHeight(i));
    }
    public Size DesiredSize
    {
      get
      {
        var a = Math.Min(_availableSize.Width, _availableSize.Height);
        if (!double.IsInfinity(a) && !double.IsNaN(a)) return new Size(a, a);

        var s = _areas[BoardArea.Cell];
        var cellSize = Math.Max(s.Width, s.Height);

        return new Size(
          cellSize * 8 + _columnsWidth + _vertEdgesWidth,
          cellSize * 8 + _rowsHeight + _horizEdgesHeight);
      }
    }

    void ISlaveLayout.SlaveMeasure(Size availableSize) { }
    void ISlaveLayout.SlaveArrange(Size arrangeSize) { }

    public void AdjustMeasures(Size arrangeSize)
    {
      RenderSize = arrangeSize;

      var w = arrangeSize.Width - _columnsWidth - _vertEdgesWidth;
      var h = arrangeSize.Height - _rowsHeight - _horizEdgesHeight;

      var min = Math.Min(w, h);
      _cellSize = min / 8;

      _spearWidth = w - min;
      _spearHeight = h - min;

      _cols[0] = GetColumnWidth(0);
      _rows[0] = GetRowHeight(0);
      for (var i = 1; i < 9; i++)
      {
        _cols[i] = _cols[i - 1] + GetColumnWidth(i);
        _rows[i] = _rows[i - 1] + GetRowHeight(i);
      }
    }

    private Dictionary<BoardLocation, Size> MeasureDic(IEnumerable<UIElement> elements)
    {
      var res = BoardLocation.All.ToDictionary(l => l, l => new Size());

      foreach (var element in elements)
      {
        var key = GetLocation(element);
        if (key.Area != BoardArea.Unknown)
          res[key] = Max(res[key], element.DesiredSize);
      }

      return res;
    }
    private static Dictionary<BoardArea, Size> MeasureAreas(Dictionary<BoardLocation, Size> dic)
    {
      var res = MeasuredAreas.ToDictionary(l => l, l => new Size());
      foreach (var pair in dic)
        foreach (var area in MeasuredAreas)
          if ((pair.Key.Area & area) != 0)
            res[area] = Max(res[area], pair.Value);
      return res;
    }

    private BoardLocation GetLocation(UIElement e)
    {
      return Transform.Tranform(BoardPanel.GetLocation(e));
    }
    private static Size Max(Size a, Size b)
    {
      return new Size(Math.Max(a.Width, b.Width), Math.Max(a.Height, b.Height));
    }

    #region ' Calculation Helpers '

    private double GetLeftBoardOffset()
    {
      return _spearWidth / 2 + _areas[BoardArea.LeftSide].Width;
    }
    private double GetTopBoardOffset()
    {
      return _spearHeight / 2 + _areas[BoardArea.TopSide].Height;
    }
    private double GetBoardHeight()
    {
      return _cellSize * 8 + _rows[8];
    }
    private double GetBoardWidth()
    {
      return _cellSize * 8 + _cols[8];
    }
    private double GetRightColumnOffset(int col)
    {
      return GetLeftBoardOffset() + _cellSize * col + _cols[col];
    }
    private double GetBottomRowOffset(int row)
    {
      return GetTopBoardOffset() + _cellSize * row + _rows[row];
    }
    private double GetLeftColumnOffset(int col)
    {
      if (col == 0) return GetLeftBoardOffset();
      return GetLeftBoardOffset() + _cellSize * col + _cols[col - 1];
    }
    private double GetTopRowOffset(int row)
    {
      if (row == 0) return GetTopBoardOffset();
      return GetTopBoardOffset() + _cellSize * row + _rows[row - 1];
    }
    private double GetColumnWidth(int col)
    {
      return Math.Max(
        _measures[BoardLocation.Column(col)].Width,
        _measures[BoardLocation.ShortColumn(col)].Width);
    }
    private double GetRowHeight(int row)
    {
      return Math.Max(
        _measures[BoardLocation.Row(row)].Height,
        _measures[BoardLocation.ShortRow(row)].Height);
    }

    #endregion

    public Rect this[BoardLocation location]
    {
      get
      {
        location = Transform.Tranform(location);
        switch (location.Area)
        {
          case BoardArea.Column:
            return new Rect(GetLeftColumnOffset(location.X), _spearHeight / 2,
                            GetColumnWidth(location.X), ActualHeight - _spearHeight);
          case BoardArea.Row:
            return new Rect(_spearWidth / 2, GetTopRowOffset(location.Y),
                            ActualWidth - _spearWidth, GetRowHeight(location.Y));
          case BoardArea.ShortColumn:
            return new Rect(GetLeftColumnOffset(location.X), GetTopBoardOffset(),
                            GetColumnWidth(location.X), GetBoardHeight());
          case BoardArea.ShortRow:
            return new Rect(GetLeftBoardOffset(), GetTopRowOffset(location.Y),
                            GetBoardWidth(), GetRowHeight(location.Y));

          case BoardArea.LeftEdgeCell:
            return new Rect(_spearWidth / 2, GetBottomRowOffset(location.Y - 1),
                            _areas[BoardArea.LeftSide].Width, _cellSize);
          case BoardArea.TopEdgeCell:
            return new Rect(GetRightColumnOffset(location.X - 1), _spearHeight / 2,
                            _cellSize, _areas[BoardArea.TopSide].Height);
          case BoardArea.RightEdgeCell:
            return new Rect(GetRightColumnOffset(8), GetBottomRowOffset(location.Y - 1),
                            _areas[BoardArea.RightSide].Width, _cellSize);
          case BoardArea.BottomEdgeCell:
            return new Rect(GetRightColumnOffset(location.X - 1), GetBottomRowOffset(8),
                            _cellSize, _areas[BoardArea.BottomSide].Height);

          case BoardArea.Cell:
            return new Rect(GetRightColumnOffset(location.X - 1),
                            GetBottomRowOffset(location.Y - 1),
                            _cellSize, _cellSize);

          case BoardArea.TopLeftCorner:
            return new Rect(_spearWidth / 2, _spearHeight / 2,
                            _areas[BoardArea.LeftSide].Width, _areas[BoardArea.TopSide].Height);
          case BoardArea.BottomLeftCorner:
            return new Rect(_spearWidth / 2, GetBottomRowOffset(8),
                            _areas[BoardArea.LeftSide].Width, _areas[BoardArea.BottomSide].Height);
          case BoardArea.BottomRightCorner:
            return new Rect(GetRightColumnOffset(8), GetBottomRowOffset(8),
                            _areas[BoardArea.RightSide].Width, _areas[BoardArea.BottomSide].Height);
          case BoardArea.TopRightCorner:
            return new Rect(GetRightColumnOffset(8), _spearHeight / 2,
                            _areas[BoardArea.RightSide].Width,
                            _areas[BoardArea.TopSide].Height);

          case BoardArea.Background:
            return new Rect(new Point(_spearWidth / 2, _spearHeight / 2), RenderSize);

          case BoardArea.Unknown:
            return new Rect();
        }
        throw new NotSupportedException();
      }
    }
  }
}